package core

import (
	"sort"
	"time"

	"github.com/ledgerwatch/erigon/core/types"
)

type ProcessCost struct {
	GasUsed       uint64
	ProcessTime   time.Duration
	ExecTime      time.Duration
	StateReadTime time.Duration
	StateReadCnt  uint64
}

func (t *ProcessCost) Add(delta *ProcessCost) {
	t.GasUsed += delta.GasUsed
	t.ProcessTime += delta.ProcessTime
	t.ExecTime += delta.ExecTime
	t.StateReadTime += delta.StateReadTime
	t.StateReadCnt += delta.StateReadCnt
}

type BlockTask struct {
	Block *types.Block
}

type TxProcessDetail struct {
	ExecTime      time.Duration
	StateReadCnt  uint64
	StateReadTime time.Duration
}

type TxProcessDetailMilli struct {
	ExecTime      float64
	StateReadCnt  uint64
	StateReadTime float64
}

// only used for geth archive, track prepare time
type DetailTxMetrics struct {
	Index       int
	PrepareCost time.Duration
	ProcessTime time.Duration

	ProcessDetail TxProcessDetail
}

type TxLevelCostLookUp struct {
	TxIndexCnt   []int
	TotalCosts   []time.Duration
	PrepareCosts []time.Duration
	ProcessCosts []time.Duration
}

func NewTxLevelCostLookUp(maxTxCnt int) *TxLevelCostLookUp {
	return &TxLevelCostLookUp{
		TxIndexCnt:   make([]int, maxTxCnt),
		TotalCosts:   make([]time.Duration, maxTxCnt),
		PrepareCosts: make([]time.Duration, maxTxCnt),
		ProcessCosts: make([]time.Duration, maxTxCnt),
	}
}

func (t *TxLevelCostLookUp) Add(idx int, prepare, process time.Duration) {
	t.TxIndexCnt[idx]++
	t.PrepareCosts[idx] += prepare
	t.ProcessCosts[idx] += process
	t.TotalCosts[idx] += prepare + process
}

func (t *TxLevelCostLookUp) AddTxCostLookUp(lookUp *TxLevelCostLookUp) {
	if len(t.TxIndexCnt) < len(lookUp.TxIndexCnt) {
		t.TxIndexCnt = append(t.TxIndexCnt, make([]int, len(lookUp.TxIndexCnt)-len(t.TxIndexCnt))...)
		t.TotalCosts = append(t.TotalCosts, make([]time.Duration, len(lookUp.TotalCosts)-len(t.TotalCosts))...)
		t.PrepareCosts = append(t.PrepareCosts, make([]time.Duration, len(lookUp.PrepareCosts)-len(t.PrepareCosts))...)
		t.ProcessCosts = append(t.ProcessCosts, make([]time.Duration, len(lookUp.ProcessCosts)-len(t.ProcessCosts))...)
	}

	for i := range t.TxIndexCnt {
		t.TxIndexCnt[i] += lookUp.TxIndexCnt[i]
		t.PrepareCosts[i] += lookUp.PrepareCosts[i]
		t.ProcessCosts[i] += lookUp.ProcessCosts[i]
		t.TotalCosts[i] += lookUp.TotalCosts[i]
	}
}

func TxCount50Percentile(blocks types.Blocks) int {
	counts := make([]int, len(blocks))
	for i, b := range blocks {
		counts[i] = len(b.Transactions())
	}
	sort.Ints(counts)
	return counts[len(counts)/2]
}

func MaxTxCnt(blocks types.Blocks) int {
	max := 0
	for _, b := range blocks {
		if max < len(b.Transactions()) {
			max = len(b.Transactions())
		}
	}
	return max
}

type BlockTaskResult struct {
	Err   error
	Block *types.Block
	ProcessCost
	DetailTxMetrics []*DetailTxMetrics
}

type BatchBlockTaskResult struct {
	BlockCnt int
	TxCnt    int
	ProcessCost
	TxLevelCostLookUp *TxLevelCostLookUp
}

func (b *BatchBlockTaskResult) Add(delta *BatchBlockTaskResult) {
	b.BlockCnt += delta.BlockCnt
	b.TxCnt += delta.TxCnt
	b.ProcessCost.Add(&delta.ProcessCost)
}

type TransactionTask struct {
	Block   *types.Block
	TxIndex int
}

type TransactionTaskResult struct {
	Err error
	*TransactionTask
	ProcessCost
}

type BatchTransactionTaskResult struct {
	TxCnt int
	ProcessCost
}

type BlockTxReplayCost struct {
	// 1. prepare 2. process
	Number    uint64
	Txs       [][2]time.Duration `json:"-"`
	TxsCost   [][2]float64
	TxDetailt []TxProcessDetailMilli `json:",omitempty"`
}

func (b *BlockTxReplayCost) FormatJSON() {
	for _, t := range b.Txs {
		b.TxsCost = append(b.TxsCost, [2]float64{
			float64(t[0]) / float64(time.Millisecond),
			float64(t[1]) / float64(time.Millisecond),
		})
	}
}
